package com.xuecheng.manage_media.service;

import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.response.CheckChunkResult;
import com.xuecheng.framework.domain.media.response.MediaCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.manage_media.config.RabbitMQConfig;
import com.xuecheng.manage_media.dao.MediaFileRepository;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;



@Service
public class MediaUploadService {
    //Day_13:注入:关于DB数据库 xc_media集合  media_file
    @Autowired
    MediaFileRepository mediaFileRepository;

    //Day_13:从yml文件中注入文件上传后存放的位置
    @Value("${xc-service-manage-media.upload-location}")
    String upload_location;

    //Day_14:从yml文件中注入路由key
    @Value("${xc-service-manage-media.mq.routingkey-media-video}")
    String routingkey_media_video;

    //Day_14:注入Template 远程发送
    @Autowired
    RabbitTemplate rabbitTemplate;


    //Day_13:得到文件所属目录路径  *(视频文件根目录)
    private String getFileFolderPath(String fileMd5){
        return upload_location+fileMd5.substring(0,1)+"/"+fileMd5.substring(1,2)+"/"+fileMd5 + "/";
    }

    //Day_13:得到文件的路径   *(视频文件整体路径)
    private String getFilePath(String fileMd5,String fileExt){
        return upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" + fileMd5 + "." +fileExt;
    }

    //Day_13:得到块文件所属目录路径    *(块文件根目录)
    private String getChunkFileFolderPath(String fileMd5){
        return  upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/chunk/";
    }

    /**
     * Day_13:文件上传前判断文件是否存在,并为上传做准备--文件夹目录
     *      根据文件md5得到文件路径
     *      规则：
     *      一级目录：md5的第一个字符
     *      二级目录：md5的第二个字符
     *      三级目录：md5
     *      文件名：md5+文件扩展名
     * @param fileMd5  文件的md5值
     * @param fileName   文件名称
     * @param fileSize  文件大小
     * @param mimetype   文件类型
     * @param fileExt  文件的扩展名
     * @return
     */
    public ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
        //获取文件目录路径 +  文件的路径
        String fileFolderPath = this.getFileFolderPath(fileMd5);
        String filePath = this.getFilePath(fileMd5, fileExt);

        //第一件事:检查文件在磁盘上是否存在
        File file = new File(filePath);
        boolean exists = file.exists();
        //第二件事:检查文件信息在mongodb中是否存在
        Optional<MediaFile> optional = mediaFileRepository.findById(fileMd5);
        if (exists && optional.isPresent()){
            //文件存在  --报错
            ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_EXIST);
        }
        //第三件事:文件不存在,提前准备创建文件目录
        File fileFolder = new File(fileFolderPath);
        if(!fileFolder.exists()){
            fileFolder.mkdirs();
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }




    /**
     * Day_13:文件上传前判断块文件是否存在
     * @param fileMd5 文件的md5值
     * @param chunk  块文件的下标
     * @param chunkSize 块的大小
     * @return    CheckChunkResult:  三件套 + 文件是否存在boolean
     */
    public CheckChunkResult checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {
        //获取块文件目录
        String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
        //第一件事:检查分块文件是否存在
        File chunkFile = new File(chunkFileFolderPath + chunk);
        if (chunkFile.exists()){
            //文件存在
            return new CheckChunkResult(CommonCode.SUCCESS,true);
        }else {
            //块文件不存在
            return new CheckChunkResult(CommonCode.SUCCESS,false);
        }
    }



    /**
     * Day_13:块文件上传
     * @param file   块文件
     * @param fileMd5   文件的md5值
     * @param chunk    第几块
     * @return
     */
    public ResponseResult uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {
        //得到分块目录
        String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
        //得到分块文件路径   目录+块文件下标
        String chunkFilePath = chunkFileFolderPath + chunk;


        //第一件事:检查该块文件是否存在
        File file1 = new File(chunkFileFolderPath);
        if (!file1.exists()){
            //第二件事:如果块文件不存在,准备目录
            file1.mkdirs();
        }

        //第三件事:copy上传文件
        //3-1:得到文件输入流
        InputStream inputStream = null;
        //3-2:得到文件输出流
        FileOutputStream outputStream = null;
        try {
             inputStream =  file.getInputStream();
             outputStream = new FileOutputStream(new File(chunkFilePath));
             //使用工具copy
            IOUtils.copy(inputStream,outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return  new ResponseResult(CommonCode.SUCCESS);
    }



    /**
     * Day_13:文件全部上传成功,合并块文件
     * @param fileMd5  文件的md5值
     * @param fileName
     * @param fileSize
     * @param mimetype
     * @param fileExt
     * @return
     */
    public ResponseResult mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
        //第一件事:合并所有分块
        //1-1:拿到块文件所在目录
        String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
        File chunkFileFolder = new File(chunkFileFolderPath);
        //分块文件列表
        File[] files = chunkFileFolder.listFiles();
        //数组转list集合
        List<File> fileList = Arrays.asList(files);

        //1-2:创建一个合并文件   自定义方法
        String filePath = this.getFilePath(fileMd5, fileExt);
        File mergeFile = new File(filePath);
        //1-3:执行合并   -- 自定义合并方法
        mergeFile = this.mergeFile(fileList, mergeFile);
        if(mergeFile == null){
            //合并文件失败
            ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
        }


        //第二件事:校验文件的md5值是否和前端传入都一致    --- 自定义校验方法
        boolean checkFileMd5 = this.checkFileMd5(mergeFile, fileMd5);
        if(!checkFileMd5){
            //校验文件失败
            ExceptionCast.cast(MediaCode.MERGE_FILE_CHECKFAIL);
        }

        //第三件事:将文件的信息写入mongodb
        MediaFile mediaFile = new MediaFile();
        mediaFile.setFileId(fileMd5);
        mediaFile.setFileOriginalName(fileName);
        mediaFile.setFileName(fileMd5 + "." +fileExt);
        //文件路径保存相对路径
        String filePath1 = fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" ;
        mediaFile.setFilePath(filePath1);
        mediaFile.setFileSize(fileSize);
        mediaFile.setUploadTime(new Date());
        mediaFile.setMimeType(mimetype);
        mediaFile.setFileType(fileExt);
        //状态为上传成功
        mediaFile.setFileStatus("301002");
        mediaFileRepository.save(mediaFile);

        //day14 想MQ发送视频处理消息
        sendProcessVideoMsg(mediaFile.getFileId());

        return new ResponseResult(CommonCode.SUCCESS);
    }

    /**
     * Day_14发送视频处理消息
     * @param mediaId 文件id
     * @return
     */
    public ResponseResult sendProcessVideoMsg (String mediaId){
        //第一件事: 判断传递的文件ID是否存在
        Optional<MediaFile> optional = mediaFileRepository.findById(mediaId);
        if (!optional.isPresent()){
            ExceptionCast.cast(CommonCode.FAIL);
        }
        //第二件事: 构建消息体
        //2-1:构建消息体
        HashMap<String, String> MQmessage = new HashMap<>();
        MQmessage.put("mediaId",mediaId);
        //2-2:map转件json
        String MQmessage_json = JSON.toJSONString(MQmessage);
        //第三件事:

        //3-2: 如果发送失败返回失败信息
        try {
            //3-1:  向MQ发送视频处理消息    参数: 交换机  路由key  消息
            rabbitTemplate.convertAndSend(RabbitMQConfig.EX_MEDIA_PROCESSTASK,routingkey_media_video,MQmessage_json);
        } catch (AmqpException e) {
            e.printStackTrace();
            return  new ResponseResult(CommonCode.FAIL);
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }



    /**
     * Day_13:检验文件
     * @param mergeFile   页面上传成功合并的文件对象
     * @param md5     页面传回来文件的md5值
     * @return
     */
    private boolean checkFileMd5(File mergeFile,String md5){

        try {
            //创建文件输入流
            FileInputStream inputStream = new FileInputStream(mergeFile);
            //得到文件的md5      DigestUtils:生成的md5,会产生同文件的大小写不同
            String md5Hex = DigestUtils.md5Hex(inputStream);
            //与前台传输的md5 进行比较   *(不区分大小写比较)
            if (md5.equalsIgnoreCase(md5Hex)){
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    //Day_13:合并文件

    /**
     * Day_13:合并文件
     * @param chunkFileList   分块文件的list集合
     * @param mergeFile       合成后存放的路径
     * @return
     */
    private File mergeFile(List<File> chunkFileList , File mergeFile){
        try {
            //第一件事:创建存储文件
            //1-1:如果合并文件存在则删除,否则删除文件
            if (mergeFile.exists()){
                //删除文件
                mergeFile.delete();
            }else {
                //1-2:创建一个新文件
                mergeFile.createNewFile();
            }
            //第二件事:合并文件
            //2-1:对文件进行排序
            Collections.sort(chunkFileList,new Comparator<File>(){

                @Override
                public int compare(File o1, File o2) {
                    if(Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){
                        return 1;
                    }
                    return -1;
                }
            });
            //2-2 进行合并读写
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");
            byte[] b = new byte[1024];
            for(File chunkFile:chunkFileList){
                RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");
                int len = -1;
                while ((len = raf_read.read(b))!=-1){
                    raf_write.write(b,0,len);
                }
                raf_read.close();
            }
            raf_write.close();
            return mergeFile;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
